﻿using System;
using System.Collections.Generic;
using System.Text;

//Copyright Pohlod Yaroslav

namespace Assembler_Compiler
{
    //The Operation Code Table (OPTAB)
    //OPTAB is used to look up mnemonic operation codes and translate them to their machine language equivalents
    static class OPTAB
    {
        //List of avaliable functions
        private static List<Function> functionsList;

        // Check if it is a assembler function
        public static bool IsCommand(String command)
        {
            //Check if it is reserved function name
            if (command == "ORG" | command == "PROC" | command == "ENDP" |
                command == "ENDS" | command == "END" | command == "RET")
            {
                return true;
            }
            else
            {
                //Check if current function exist at functions list
                return OPTAB.functionsList.Find(c => c.GetFuncName() == command) != null;
            }
        }

        //Check if it is non compiled command
        public static bool IsNoCompiledCommand(String command)
        {
            if (command == "ORG" | command == "ENDP" | command == "ENDS" |
                command == "END" | command == "PROC")
                return true;
            else
                return false;
        }

        // Gets length in bytes for current command with operands
        public static Int16 GetFullCommandLenght(String command, String[] operands, String format, Int16 w)
        {
            // get full function info of current command
            Function currentFunction = functionsList.Find(c => c.GetFuncName() == command);
            // throw exception if there are no such function
            if (currentFunction == null)
                throw new Exception("(GetFullCommandLenght)There are no such command: " + command);
            // count of eleements
            Int16 count = 1;
            // add count of operands if it exist
            if (operands != null)
            {
                if (operands[0].StartsWith("'") == true & operands[0].EndsWith("'") == true)
                {
                    count = (Int16)(operands[0].Length - 2);
                }
            }
            // return multiplication of count of elements on it size
            return (Int16)(count * currentFunction.GetSize(format, w));
        }

        //Get width of operands
        public static Int16 GetWValue(String[] operands)
        {
            if (operands == null)
                return 0;
            // maximum operand size
            Int16 size = 3;
            // search minimum operand size
            foreach (String operand in operands)
            {
                size = Math.Min(size, GetOperandSize(operand));
            }
            if (size == 1)
                return 0;
            if (size == 2)
                return 1;
            throw new Exception("(GetWValue) Wrong operand size: " + size);
        }

        // Get size of current operand
        private static Int16 GetOperandSize(String operand)
        {
            // check type of operand to get if size
            if (operand.Length == 2)
            {
                if (
                    (operand[0] == 'A' & operand[1] == 'L') |
                    (operand[0] == 'B' & operand[1] == 'L') |
                    (operand[0] == 'C' & operand[1] == 'L') |
                    (operand[0] == 'D' & operand[1] == 'L') |
                    (operand[0] == 'A' & operand[1] == 'H') |
                    (operand[0] == 'B' & operand[1] == 'H') |
                    (operand[0] == 'C' & operand[1] == 'H') |
                    (operand[0] == 'D' & operand[1] == 'H')
                    )
                    return 1;
                if (
                    (operand[0] == 'A' & operand[1] == 'X') |
                    (operand[0] == 'B' & operand[1] == 'X') |
                    (operand[0] == 'C' & operand[1] == 'X') |
                    (operand[0] == 'D' & operand[1] == 'X') |
                    (operand[0] == 'S' & operand[1] == 'I') |
                    (operand[0] == 'D' & operand[1] == 'I') |
                    (operand[0] == 'B' & operand[1] == 'P') |
                    (operand[0] == 'S' & operand[1] == 'P')
                    )
                    return 2;
            }
            // check if it is a decimal or hexadecimal number
            if (operand.EndsWith("H") == true |
                operand.EndsWith("h") == true |
                operand.EndsWith("D") == true |
                operand.EndsWith("d") == true |
                ConverterClass.IsDecimal(operand) == true)
            {
                Int16 value = ConverterClass.StringToInt(operand);
                if (value < 256)
                    return 1;
                else
                    return 2;
            }
            return 2;
        }

        // Get operands wtited as one firmatted string
        public static String OperandsFormating(String[] operands)
        {
            String result = "";
            if (operands != null)
                switch (operands.Length)
                {
                    case 0:
                        break;
                    case 1:
                        result += GetOperandType(operands[0]);
                        break;
                    case 2:
                        result += GetOperandType(operands[0]);
                        result += "_";
                        result += GetOperandType(operands[1]);
                        break;
                    case 3:
                        result += GetOperandType(operands[0]);
                        result += "_";
                        result += GetOperandType(operands[1]);
                        result += "_";
                        result += GetOperandType(operands[2]);
                        break;
                }
            return result;
        }

        // Gets type of current operand
        private static String GetOperandType(String operand)
        {
            if (operand.Length >= 2)
            {
                // Chech if it is register operand
                if ((operand[0] == 'A' & operand[1] == 'L') |
                    (operand[0] == 'B' & operand[1] == 'L') |
                    (operand[0] == 'C' & operand[1] == 'L') |
                    (operand[0] == 'D' & operand[1] == 'L') |
                    (operand[0] == 'A' & operand[1] == 'H') |
                    (operand[0] == 'B' & operand[1] == 'H') |
                    (operand[0] == 'C' & operand[1] == 'H') |
                    (operand[0] == 'D' & operand[1] == 'H') |
                    (operand[0] == 'A' & operand[1] == 'X') |
                    (operand[0] == 'B' & operand[1] == 'X') |
                    (operand[0] == 'C' & operand[1] == 'X') |
                    (operand[0] == 'D' & operand[1] == 'X') |
                    (operand[0] == 'S' & operand[1] == 'I') |
                    (operand[0] == 'D' & operand[1] == 'I') |
                    (operand[0] == 'B' & operand[1] == 'P') |
                    (operand[0] == 'S' & operand[1] == 'P'))
                {
                    return "R";
                }
                // Check if it memory operand (has at SYMTAB)
                else if (SYMTAB.Contain(operand) == true)
                {
                    return "M";
                }
                // Check if it is number les than 10
                else if (ConverterClass.IsDecimal(operand) == true)
                {
                    return "I";
                }
                // Check if it is decimal or hexadecimal number
                else if (operand.EndsWith("H") == true |
                    operand.EndsWith("h") == true |
                    operand.EndsWith("D") == true |
                    operand.EndsWith("d") == true)
                {
                    if (ConverterClass.IsHexadecimal(operand.Remove(operand.Length - 1)) == true)
                        return "I";
                    else
                        throw new Exception("(GetOperandType)Hexadecimal type crash: " + operand);
                }
                else if (operand.StartsWith("'") == true & operand.EndsWith("'") == true)
                {
                    return "I";
                }
                else
                    return "M";
            }
            else
            {
                // Check if it is number less than 10
                if (ConverterClass.IsDecimal(operand) == true)
                {
                    return "I";
                }
                if (operand[0] == '?')
                    return "I";
                else
                    return "M";
            }
        }

        // Initaalization of OPTAB
        public static void Initialize()
        {
            // command possition
            int i = 0;
            functionsList = new List<Function>();
            //MOV comands
            functionsList.Add(new Function("MOV"));
            functionsList[i].AddFormat("R_R", new String[] { "1000100w", "modregr/m" });
            functionsList[i].AddPairToFormat("R_R", 0, 2);
            functionsList[i].AddPairToFormat("R_R", 1, 2);
            functionsList[i].AddFormat("M_R", new String[] { "1000100w", "modregr/m" });
            functionsList[i].AddPairToFormat("M_R", 0, 4);
            functionsList[i].AddPairToFormat("M_R", 1, 4);
            functionsList[i].AddFormat("R_M", new String[] { "1000101w", "modregr/m" });
            functionsList[i].AddPairToFormat("R_M", 0, 4);
            functionsList[i].AddPairToFormat("R_M", 1, 4);
            functionsList[i].AddFormat("R_I", new String[] { "1011wreg" });
            functionsList[i].AddPairToFormat("R_I", 0, 2);
            functionsList[i].AddPairToFormat("R_I", 1, 3);
            i++;
            //LEA comands
            functionsList.Add(new Function("LEA"));
            functionsList[i].AddFormat("R_M", new String[] { "10001101", "modregr/m" });
            functionsList[i].AddPairToFormat("R_M", 1, 4);
            i++;
            //INT comands
            functionsList.Add(new Function("INT"));
            functionsList[i].AddFormat("I", new String[] { "11001101" });
            functionsList[i].AddPairToFormat("I", 0, 2);
            functionsList[i].AddPairToFormat("I", 1, 2);
            i++;
            //JMP comands
            functionsList.Add(new Function("JMP"));
            functionsList[i].AddFormat("M", new String[] { "11101011" });
            functionsList[i].AddPairToFormat("M", 1, 3);
            i++;

            //XCHG comands
            functionsList.Add(new Function("XCHG"));
            functionsList[i].AddFormat("R_R", new String[] { "1000011w", "modregr/m" });
            functionsList[i].AddPairToFormat("R_R", 1, 2);
            functionsList[i].AddPairToFormat("R_R", 0, 2);
            i++;

            //RET comands
            functionsList.Add(new Function("RET"));
            functionsList[i].AddFormat("", new String[] { "11000011" });
            functionsList[i].AddPairToFormat("", 1, 1);
            functionsList[i].AddPairToFormat("", 0, 1);
            i++;

            //DB comands
            functionsList.Add(new Function("DB"));
            functionsList[i].AddFormat("I", null);
            functionsList[i].AddPairToFormat("I", 0, 1);
            functionsList[i].AddPairToFormat("I", 1, 1);
            i++;
            //DW comands
            functionsList.Add(new Function("DW"));
            functionsList[i].AddFormat("I", null);
            functionsList[i].AddPairToFormat("I", 1, 2);
            i++;
            //DD comands
            functionsList.Add(new Function("DD"));
            functionsList[i].AddFormat("I", null);
            functionsList[i].AddPairToFormat("I", 1, 4);
        }

        // Compile assembler comand with operands to hexadecimal machine code
        public static byte[] GetMachineCode(String command, String format, Int16 W, String[] operands, Int16 lenght, Int16 addressOfLine)
        {
            // Search function at function list
            Function function = functionsList.Find(c => c.GetFuncName() == command);
            if (function == null)
                throw new Exception("(GetMachineCode)There is no command: " + command + " in functions list");
            // allocate array of bytes for result
            byte[] result = new byte[lenght];
            byte[] operandsBytes = null;
            // if command is directive         
            if (command == "DW" | command == "DD" | command == "DB")
            {
                if (command == "DB")
                {
                    // if local variable
                    if (operands[0].StartsWith("'") == true & operands[0].EndsWith("'") == true)
                    {
                        String value = operands[0].Remove(0, 1);
                        value = value.Remove(value.Length - 1);
                        byte[] textBytes = System.Text.ASCIIEncoding.ASCII.GetBytes(value);
                        for (int i = 0; i < textBytes.Length; i++)
                        {
                            result[i] = textBytes[i];
                        }
                    }
                    else
                        result = GetOperandMachineCode(operands[0]);
                }
                if (command == "DW")
                {
                    byte[] valueBytes = GetOperandMachineCode(operands[0]);
                    if (valueBytes.Length > 2)
                        throw new Exception("DW operand have bad value");
                    if (valueBytes.Length < 2)
                    {
                        byte[] resized = new byte[valueBytes.Length + 1];
                        for (int i = 0; i < valueBytes.Length - 1; i++)
                        {
                            resized[i] = valueBytes[i];
                        }
                        result = resized;
                    }
                    else
                        result = valueBytes;
                }
            }
            // if comand is jump 
            else if (command == "JMP")
            {
                String[] commandCode = function.GetMachineCode(format);
                Int16 address = SYMTAB.GetAddress(operands[0]);
                if (address - addressOfLine - lenght + 1 < 256)
                {
                    address -= (Int16)(addressOfLine + lenght - 1);
                    operandsBytes = new byte[] { Convert.ToByte(address) , 0x90 };
                }
                else
                {
                    operandsBytes = GetOperandMachineCode(operands[0]);
                }
                byte[] commandBytes = GetCommandMachineCode(commandCode);
                int q = 0;
                foreach (byte b in commandBytes)
                {
                    result[q++] = b;
                }
                foreach (byte b in operandsBytes)
                {
                    result[q++] = b;
                }
            }
            else
            {
                String[] commandCode = function.GetMachineCode(format);
                String mod;
                String reg;
                String r_m;
                String w = Convert.ToString(W);
                switch (format)
                {
                    case "R_R":
                        mod = "11";
                        reg = GetRegisterString(operands[0]);
                        r_m = GetRegisterString(operands[1]);
                        break;
                    case "R_M":
                        mod = "00";
                        reg = GetRegisterString(operands[0]);
                        r_m = "110";
                        operandsBytes = GetAddressMachineCode(operands[1]);
                        break;
                    case "M_R":
                        mod = "00";
                        reg = GetRegisterString(operands[1]);
                        r_m = "110";
                        operandsBytes = GetAddressMachineCode(operands[0]);
                        break;
                    case "R_I":
                        mod = "";
                        reg = GetRegisterString(operands[0]);
                        r_m = "";
                        operandsBytes = GetOperandMachineCode(operands[1]);
                        break;
                    case "I":
                        mod = "";
                        reg = "";
                        r_m = "";
                        operandsBytes = GetOperandMachineCode(operands[0]);
                        break;
                    case "M":
                        mod = "";
                        reg = "";
                        r_m = "";
                        operandsBytes = GetAddressMachineCode(operands[0]);
                        break;
                    case "":
                        mod = "";
                        reg = "";
                        r_m = "";
                        operandsBytes = null;
                        break;
                    default:
                        throw new Exception("Bad getting machine code in GetMachineCode");

                }
                // replace default w mod reg and r/m values with real values
                for (int i = 0; i < commandCode.Length; i++)
                {
                    commandCode[i] = commandCode[i].Replace("w", w);
                    commandCode[i] = commandCode[i].Replace("mod", mod);
                    commandCode[i] = commandCode[i].Replace("reg", reg);
                    commandCode[i] = commandCode[i].Replace("r/m", r_m);
                }
                byte[] commandBytes = GetCommandMachineCode(commandCode);
                int q = 0;
                foreach (byte b in commandBytes)
                {
                    result[q++] = b;
                }
                if (operandsBytes != null)
                {
                    operandsBytes = ReverseBytes(operandsBytes);
                    foreach (byte b in operandsBytes)
                    {
                        result[q++] = b;
                    }
                }
            }
            return result;
        }

        private static byte[] ReverseBytes(byte[] operandBytes)
        {
            byte[] result = new byte[operandBytes.Length];
            int i = operandBytes.Length - 1;
            foreach (byte b in operandBytes)
            {
                result[i--] = b;
            }
            return result;
        }

        // Get machine code of command
        private static byte[] GetCommandMachineCode(String[] command)
        {
            byte[] result = new byte[command.Length];
            int i = 0;
            //convert each string at command
            foreach (String str in command)
            {
                Int16 temp = ConverterClass.FromBinaryStringToInt(str);
                result[i++] = Convert.ToByte(temp);
            }
            return result;
        }

        // Get machine code of operands
        private static byte[] GetOperandMachineCode(String operandStr)
        {
            Int16 operand = ConverterClass.StringToInt(operandStr);
            // if operand has to bytes divide it
            if (operand >= 256)
            {
                byte temp1 = Convert.ToByte(operand / 256);
                byte temp2 = Convert.ToByte(operand % 256);
                return new byte[] { temp1, temp2 };
            }
            else
            {
                return new byte[] { Convert.ToByte(operand) };
            }
        }

        // get machine code of operands
        private static byte[] GetAddressMachineCode(String addressStr)
        {
            Int16 address = SYMTAB.GetAddress(addressStr);
            // adress always over 256 so divide it
            byte temp1 = Convert.ToByte(address / 256);
            byte temp2 = Convert.ToByte(address % 256);
            return new byte[] { temp1, temp2 };
        }

        // Get string with code for ccurrent register
        private static String GetRegisterString(String reg)
        {
            if (reg[0] == 'A' & (reg[1] == 'L' | reg[1] == 'X'))
                return "000";
            if (reg[0] == 'C' & (reg[1] == 'L' | reg[1] == 'X'))
                return "001";
            if (reg[0] == 'D' & (reg[1] == 'L' | reg[1] == 'X'))
                return "010";
            if (reg[0] == 'B' & (reg[1] == 'L' | reg[1] == 'X'))
                return "011";
            if ((reg[0] == 'A' & reg[1] == 'H') | (reg[0] == 'S' & reg[1] == 'P'))
                return "100";
            if ((reg[0] == 'C' & reg[1] == 'H') | (reg[0] == 'B' & reg[1] == 'P'))
                return "101";
            if ((reg[0] == 'D' & reg[1] == 'H') | (reg[0] == 'S' & reg[1] == 'I'))
                return "110";
            if ((reg[0] == 'B' & reg[1] == 'H') | (reg[0] == 'D' & reg[1] == 'I'))
                return "111";
            throw new Exception("(GetRegisterString)Bad register name: " + reg);
        }
    }
}
